Skip to content

feat: expand secret/env refs in all ServerConfig string fields and DataDir#336

Merged
Dumbris merged 3 commits intosmart-mcp-proxy:mainfrom
tjsingleton:034-expand-secret-refs
Mar 11, 2026
Merged

feat: expand secret/env refs in all ServerConfig string fields and DataDir#336
Dumbris merged 3 commits intosmart-mcp-proxy:mainfrom
tjsingleton:034-expand-secret-refs

Conversation

@tjsingleton
Copy link
Contributor

Summary

Fixes the silent expansion gap where ${env:...} and ${keyring:...} references were only expanded for Env, Args, and Headers in NewClientWithOptions. Fields like WorkingDir, URL, Command, and all IsolationConfig/OAuthConfig fields were silently ignored.

Related #333

Problem

The manual allowlist approach guarantees recurrence: every new string field added to ServerConfig requires a manual code change to get expansion. Users configuring "working_dir": "${env:IH_HOME}" get the literal string, causing server startup failures.

Approach

Replace the ~78-line manual expansion block with a new ExpandStructSecretsCollectErrors reflection-based method that:

  • Automatically covers all current and future string fields
  • Collects errors instead of failing fast (preserves existing log-and-continue semantics)
  • Tracks full field paths for rich error messages ("Isolation.WorkingDir", "Args[0]", "Env[MY_VAR]")
  • Never logs resolved values (FR-003, security)

Also expands Config.DataDir in loader.go before validation.

Changes (planned — implementation not yet started)

  • internal/secret/resolver.go — add SecretExpansionError type + ExpandStructSecretsCollectErrors method
  • internal/config/merge.go — export CopyServerConfig (deep copy before expansion)
  • internal/upstream/core/client.go — replace lines 105–182 with struct expansion (~12 lines)
  • internal/upstream/core/client_secret_test.go — new: reflection regression test (SC-004)
  • internal/config/loader.go — expand DataDir at 2 call sites before cfg.Validate()
  • internal/config/config_test.go — DataDir expansion tests

Spec Artifacts

Test plan

  • T001 Baseline: all existing tests pass before changes
  • T002–T006 Foundational: ExpandStructSecretsCollectErrors tests written and passing
  • T007–T010 US1+US2: NewClientWithOptions expansion tests + reflection regression test passing
  • T011–T013 US3: DataDir expansion tests passing
  • T015 Full regression: go test ./internal/... -race
  • T016 E2E: ./scripts/test-api-e2e.sh
  • T017 Manual smoke: server with "working_dir": "${env:HOME}/test" starts with resolved path

Related smart-mcp-proxy#333

Adds the full spec-driven development artifacts for expanding
${env:...} and ${keyring:...} reference support to all string
fields in ServerConfig and Config.DataDir.

## Artifacts
- specs/034-expand-secret-refs/spec.md — user stories, FRs, SCs
- specs/034-expand-secret-refs/research.md — 5 key design decisions
- specs/034-expand-secret-refs/plan.md — TDD implementation plan
- specs/034-expand-secret-refs/tasks.md — 17 ordered tasks
- specs/034-expand-secret-refs/checklists/requirements.md — all pass
…verConfig

Related smart-mcp-proxy#333

Foundational phase for expanding secret refs in all ServerConfig fields.

## Changes
- Add SecretExpansionError type to internal/secret/resolver.go
- Add ExpandStructSecretsCollectErrors method: mirrors expandValue with
  path tracking and collect-errors semantics (no fail-fast)
- Export copyServerConfig -> CopyServerConfig in internal/config/merge.go
  (update 3 internal call sites)

## Tests
- 7 new tests in resolver_test.go: HappyPath, PartialFailure, NilPointer,
  NestedStruct, SliceField, MapField, NoRefs — all pass
- All existing config merge tests pass
… fields

Replace the manual 78-line field-by-field expansion allowlist in
NewClientWithOptions with reflection-based ExpandStructSecretsCollectErrors,
which automatically covers all current and future string fields in
ServerConfig (including nested IsolationConfig and OAuthConfig).

Also expands ${env:...} refs in Config.DataDir before os.MkdirAll so the
directory is created at the resolved path.

Changes:
- internal/secret/resolver.go: add SecretExpansionError + ExpandStructSecretsCollectErrors (collects errors, preserves unresolved values on failure)
- internal/config/merge.go: export CopyServerConfig (deep copy with Isolation/OAuth pointer fields)
- internal/upstream/core/client.go: replace manual expansion block with CopyServerConfig + ExpandStructSecretsCollectErrors + error logging
- internal/config/loader.go: add expandDataDir helper, call before MkdirAll in LoadFromFile and Load
- Tests: resolver_test.go (7 cases), client_secret_test.go (5 + reflection regression), config_test.go (DataDir expand + failure)
@tjsingleton tjsingleton marked this pull request as ready for review March 10, 2026 23:56
@Dumbris
Copy link
Member

Dumbris commented Mar 11, 2026

Thank you for this excellent contribution! 🎉

The reflection-based ExpandStructSecretsCollectErrors approach is the right call — it eliminates the entire class of "forgot to add a new field to the expansion allowlist" bugs. The spec artifacts, test coverage (especially the reflection regression test), and deep copy fix are all well done.

The CI failures are pre-existing flaky quarantine tests (TestCheckToolApprovals_NewTool_PendingStatus, TestApproveTools, TestApproveAllTools) — not related to your changes. Re-ran them and merging.

Thanks again for the solid work!

@Dumbris Dumbris merged commit 94779d6 into smart-mcp-proxy:main Mar 11, 2026
26 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants